import tkinter as tk
from tkinter import messagebox
import pyautogui
import keyboard
from pynput import mouse
import threading
import time
import json
import os
import datetime
import random
import platform

def get_json_file_path():
    system = platform.system()
    if system == "Windows":
        user_dir = os.path.expanduser("~")  # Windows 用户目录
        folder = os.path.join(user_dir, ".auto_click")
    else:  # Linux 或其他
        folder = "/home/auto_click"

    if not os.path.exists(folder):
        os.makedirs(folder, exist_ok=True)

    return os.path.join(folder, "click_points.json")

points = []
recording = False
listener = None
stop_replay_flag = False
json_file = get_json_file_path()
config_key = "_config"

default_config = {
    "pause": 0,
    "minimum_sleep": 0,
    "minimum_wait": 0,
    "move_duration": 0.1,
    "replay_delay": 0
}

# ============ 日志 ============
def log(msg: str):
    timestamp = datetime.datetime.now().strftime("%H:%M:%S")
    log_text.insert(tk.END, f"[{timestamp}] {msg}\n")
    log_text.see(tk.END)
    print(msg)

# ============ JSON 操作 ============
def init_json_file():
    if not os.path.exists(json_file):
        with open(json_file, "w", encoding="utf-8") as f:
            json.dump({config_key: default_config}, f, ensure_ascii=False, indent=4)
        log(f"已创建文件：{json_file}")

def load_all_data():
    init_json_file()
    with open(json_file, "r", encoding="utf-8") as f:
        return json.load(f)

def save_all_data(data):
    with open(json_file, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

def save_points_to_json(name, points):
    data = load_all_data()
    data[name] = points
    save_all_data(data)
    refresh_saved_list()
    log(f"已保存点位：{name}（共 {len(points)} 个）")

def load_points_from_json(name):
    data = load_all_data()
    return data.get(name, [])

def delete_points_from_json(name):
    data = load_all_data()
    if name in data:
        del data[name]
        save_all_data(data)
        refresh_saved_list()
        messagebox.showinfo("提示", f"已删除点位：{name}", parent=root)
        log(f"已删除点位：{name}")

def refresh_saved_list():
    data = load_all_data()
    listbox.delete(0, tk.END)
    for name in data.keys():
        if name != config_key:
            listbox.insert(tk.END, name)

# ============ 配置 ============
def load_config():
    data = load_all_data()
    cfg = data.get(config_key, default_config.copy())
    apply_pyautogui_config(cfg)
    return cfg

def save_config(cfg):
    data = load_all_data()
    data[config_key] = cfg
    save_all_data(data)
    apply_pyautogui_config(cfg)
    log("配置已保存并应用。")

def apply_pyautogui_config(cfg):
    pyautogui.PAUSE = cfg.get("pause", 0)
    pyautogui.MINIMUM_SLEEP = cfg.get("minimum_sleep", 0)
    pyautogui.MINIMUM_WAIT = cfg.get("minimum_wait", 0)
    log(f"鼠标模拟器 配置已应用：PAUSE={pyautogui.PAUSE}, SLEEP={pyautogui.MINIMUM_SLEEP}, WAIT={pyautogui.MINIMUM_WAIT}")

# ============ 窗口居中 ============
def center_window(win, width, height):
    win.update_idletasks()
    x = root.winfo_x() + (root.winfo_width() // 2) - (width // 2)
    y = root.winfo_y() + (root.winfo_height() // 2) - (height // 2)
    win.geometry(f"{width}x{height}+{x}+{y}")

# ============ 设置窗口 ============
def open_settings():
    cfg = load_config()
    settings_win = tk.Toplevel(root)
    settings_win.title("系统设置")
    settings_win.transient(root)
    settings_win.grab_set()
    center_window(settings_win, 320, 300)

    tk.Label(settings_win, text="鼠标模拟器 全局参数", font=("微软雅黑", 10, "bold")).pack(pady=8)
    frame = tk.Frame(settings_win)
    frame.pack(pady=5)

    tk.Label(frame, text="点击后等待(PAUSE)：").grid(row=0, column=0, sticky="e")
    entry_pause = tk.Entry(frame, width=8)
    entry_pause.insert(0, str(cfg.get("pause", 0)))
    entry_pause.grid(row=0, column=1, padx=5)

    tk.Label(frame, text="最小睡眠(SLEEP)：").grid(row=1, column=0, sticky="e")
    entry_sleep = tk.Entry(frame, width=8)
    entry_sleep.insert(0, str(cfg.get("minimum_sleep", 0)))
    entry_sleep.grid(row=1, column=1, padx=5)

    tk.Label(frame, text="最小等待(WAIT)：").grid(row=2, column=0, sticky="e")
    entry_wait = tk.Entry(frame, width=8)
    entry_wait.insert(0, str(cfg.get("minimum_wait", 0)))
    entry_wait.grid(row=2, column=1, padx=5)

    tk.Label(settings_win, text="鼠标轨迹持续时间(s)：", font=("微软雅黑", 10, "bold")).pack(pady=10)
    entry_move = tk.Entry(settings_win, width=10)
    entry_move.insert(0, str(cfg.get("move_duration", 0.1)))
    entry_move.pack()

    tk.Label(settings_win, text="重播延迟时间(s)：", font=("微软雅黑", 10, "bold")).pack(pady=10)
    entry_delay = tk.Entry(settings_win, width=10)
    entry_delay.insert(0, str(cfg.get("replay_delay", 0)))
    entry_delay.pack()

    def save_changes():
        try:
            cfg["pause"] = float(entry_pause.get())
            cfg["minimum_sleep"] = float(entry_sleep.get())
            cfg["minimum_wait"] = float(entry_wait.get())
            cfg["move_duration"] = float(entry_move.get())
            cfg["replay_delay"] = float(entry_delay.get())
            save_config(cfg)
            messagebox.showinfo("提示", "设置已保存！", parent=settings_win)
            settings_win.destroy()
        except ValueError:
            messagebox.showerror("错误", "请输入有效数字！", parent=settings_win)

    tk.Button(settings_win, text="保存设置", command=save_changes, width=12).pack(pady=15)

# ============ 鼠标录制 ============
def start_recording():
    global recording, points, listener
    if recording:
        messagebox.showwarning("提示", "正在记录中，请按 ESC 停止。", parent=root)
        return

    points.clear()
    recording = True
    messagebox.showinfo("提示", "进入点位记录模式。\n点击屏幕任意位置以记录。\n按 ESC 停止。", parent=root)
    log("开始记录点位...")

    listener = mouse.Listener(on_click=on_mouse_click)
    listener.start()
    threading.Thread(target=listen_for_escape_record, daemon=True).start()

def on_mouse_click(x, y, button, pressed):
    if pressed:
        points.append((x, y))
        log(f"记录点位：({x}, {y})")

def listen_for_escape_record():
    global recording, listener
    keyboard.wait("esc")
    if recording:
        recording = False
        if listener:
            listener.stop()
        root.after(100, prompt_save_points)

def prompt_save_points():
    if not points:
        messagebox.showinfo("提示", "没有记录到任何点位。", parent=root)
        log("未记录到任何点位。")
        return

    save_dialog = tk.Toplevel(root)
    save_dialog.title("保存点位")
    save_dialog.transient(root)
    save_dialog.grab_set()
    center_window(save_dialog, 280, 120)

    tk.Label(save_dialog, text="请输入保存名称：").pack(pady=10)
    entry_name = tk.Entry(save_dialog)
    entry_name.pack(pady=5)
    entry_name.focus_set()

    def confirm():
        name = entry_name.get().strip()
        if name:
            save_points_to_json(name, points)
            messagebox.showinfo("提示", f"点位已保存：{name}", parent=save_dialog)
            log(f"已保存点位集：{name}")
            save_dialog.destroy()
        else:
            messagebox.showwarning("提示", "名称不能为空。", parent=save_dialog)
            log("保存失败：名称为空。")

    tk.Button(save_dialog, text="确定保存", command=confirm, width=15).pack(pady=10)

# ============ 重播逻辑 ============
def start_replay(name, points, repeat_count, interval, radius, simulate_path, cfg):
    threading.Thread(
        target=lambda: perform_replay(name, points, repeat_count, interval, radius, simulate_path, cfg),
        daemon=True,
    ).start()

def replay_clicks():
    selected = listbox.get(tk.ACTIVE)
    if not selected:
        messagebox.showwarning("提示", "请选择要重播的点位！", parent=root)
        return

    loaded_points = load_points_from_json(selected)
    if not loaded_points:
        messagebox.showwarning("提示", "选定点位为空或不存在！", parent=root)
        return

    try:
        repeat_count = int(entry_repeat.get())
        interval = float(entry_interval.get()) / 1000.0
        radius = float(entry_radius.get())
    except ValueError:
        messagebox.showerror("错误", "请输入有效数字！", parent=root)
        return

    simulate_path = var_simulate_path.get() == 1
    cfg = load_config()
    replay_delay = cfg.get("replay_delay", 0)

    if replay_delay > 0:
        show_countdown_window(selected, loaded_points, repeat_count, interval, radius, simulate_path, cfg, replay_delay)
    else:
        start_replay(selected, loaded_points, repeat_count, interval, radius, simulate_path, cfg)

def show_countdown_window(name, points, repeat_count, interval, radius, simulate_path, cfg, delay):
    countdown_win = tk.Toplevel(root)
    countdown_win.title("重播倒计时")
    countdown_win.transient(root)
    countdown_win.grab_set()
    center_window(countdown_win, 260, 140)

    tk.Label(countdown_win, text=f"{delay} 秒后开始重播...", font=("微软雅黑", 12)).pack(pady=15)
    label_count = tk.Label(countdown_win, text="", font=("微软雅黑", 20, "bold"), fg="red")
    label_count.pack(pady=10)

    cancel_flag = {"cancel": False}

    def on_close():
        cancel_flag["cancel"] = True
        countdown_win.destroy()
        log("重播已取消。")

    countdown_win.protocol("WM_DELETE_WINDOW", on_close)
    tk.Button(countdown_win, text="取消重播", command=on_close, width=12).pack(pady=5)

    def countdown_task():
        nonlocal delay
        while delay > 0 and not cancel_flag["cancel"]:
            label_count.config(text=str(int(delay)))
            time.sleep(1)
            delay -= 1
        if not cancel_flag["cancel"]:
            countdown_win.destroy()
            start_replay(name, points, repeat_count, interval, radius, simulate_path, cfg)

    threading.Thread(target=countdown_task, daemon=True).start()

def perform_replay(name, points, repeat_count, interval, radius, simulate_path, cfg):
    global stop_replay_flag
    stop_replay_flag = False
    log(f"即将重播 [{name}]：点位 {len(points)} 个，循环 {repeat_count} 次，间隔 {interval*1000:.0f}ms，范围 {radius}px，轨迹 {'开' if simulate_path else '关'}")
    threading.Thread(target=listen_for_escape_replay, daemon=True).start()
    time.sleep(0.5)

    for i in range(repeat_count):
        if stop_replay_flag:
            messagebox.showinfo("提示", "重播已中止。", parent=root)
            log("用户按 ESC 中止重播。")
            return

        log(f"开始第 {i+1} 次重播...")
        for (x, y) in points:
            if stop_replay_flag:
                messagebox.showinfo("提示", "重播已中止。", parent=root)
                log("用户按 ESC 中止重播。")
                return
            new_x = x + random.uniform(-radius, radius)
            new_y = y + random.uniform(-radius, radius)
            if simulate_path:
                pyautogui.moveTo(new_x, new_y, duration=min(cfg.get("move_duration", 0.1), interval))
                pyautogui.click()
            else:
                pyautogui.click(new_x, new_y)
            time.sleep(interval)
        log(f"第 {i+1} 次重播完成。")

    messagebox.showinfo("提示", "重播完成！", parent=root)
    log(f"点位集 [{name}] 重播完成。")

def listen_for_escape_replay():
    global stop_replay_flag
    keyboard.wait("esc")
    stop_replay_flag = True

def set_window_icon(root):
    base_path = os.path.dirname(os.path.abspath(__file__))

    # 1. Windows 优先使用 .ico
    ico_path = os.path.join(base_path, "window.ico")
    if os.path.exists(ico_path):
        try:
            root.iconbitmap(ico_path)
        except Exception as e:
            print("ICO 图标加载失败：", e)

    # 2. 跨平台使用 PNG
    png_path = os.path.join(base_path, "app.png")
    if os.path.exists(png_path):
        try:
            icon_img = tk.PhotoImage(file=png_path)
            root.iconphoto(True, icon_img)
        except Exception as e:
            print("PNG 图标加载失败：", e)

# ============ GUI ============
root = tk.Tk()
root.title("鼠标点击模拟器")
root.geometry("580x640")
# 设置图标
set_window_icon(root)
root.attributes("-topmost", True)

tk.Button(root, text="开始获取点位 (ESC停止)", command=start_recording, width=25).pack(pady=10)

frame_params = tk.LabelFrame(root, text="参数设置", padx=10, pady=8)
frame_params.pack(pady=5, fill="x", padx=10)

tk.Label(frame_params, text="重播次数：").grid(row=0, column=0, sticky="e")
entry_repeat = tk.Entry(frame_params, width=8)
entry_repeat.insert(0, "1")
entry_repeat.grid(row=0, column=1, padx=5)

tk.Label(frame_params, text="点击间隔(毫秒)：").grid(row=0, column=2, sticky="e")
entry_interval = tk.Entry(frame_params, width=8)
entry_interval.insert(0, "500")
entry_interval.grid(row=0, column=3, padx=5)

tk.Label(frame_params, text="随机范围(px)：").grid(row=0, column=4, sticky="e")
entry_radius = tk.Entry(frame_params, width=8)
entry_radius.insert(0, "0")
entry_radius.grid(row=0, column=5, padx=5)

tk.Label(root, text="已保存点位：").pack(pady=5)
listbox = tk.Listbox(root, height=8, width=55)
listbox.pack()

frame_btns = tk.Frame(root)
frame_btns.pack(pady=5)
tk.Button(frame_btns, text="重播选中(ESC停止)", command=replay_clicks, width=16).grid(row=0, column=0, padx=5)
tk.Button(frame_btns, text="删除选中", command=lambda: delete_points_from_json(listbox.get(tk.ACTIVE)), width=12).grid(row=0, column=1, padx=5)
tk.Button(frame_btns, text="设置", command=open_settings, width=12).grid(row=0, column=2, padx=5)

frame_options = tk.Frame(root)
frame_options.pack(pady=5)
var_topmost = tk.IntVar(value=1)
chk_topmost = tk.Checkbutton(frame_options, text="窗口置顶", variable=var_topmost,
                             command=lambda: root.attributes("-topmost", var_topmost.get()==1))
chk_topmost.pack(side=tk.LEFT, padx=5)
var_simulate_path = tk.IntVar(value=0)
chk_simulate_path = tk.Checkbutton(frame_options, text="模拟鼠标轨迹", variable=var_simulate_path)
chk_simulate_path.pack(side=tk.LEFT, padx=10)

tk.Label(root, text="运行日志：").pack(pady=(5,0))
log_frame = tk.Frame(root)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
log_text = tk.Text(log_frame, height=10, wrap=tk.WORD)
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = tk.Scrollbar(log_frame, command=log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=scrollbar.set)

# 初始化配置
load_config()
refresh_saved_list()
log("程序启动完成。")

root.mainloop()
